Skip to content

feat(think): add experimental Computer workspace - #2002

Closed
cjol wants to merge 4 commits into
mainfrom
think-workspace
Closed

feat(think): add experimental Computer workspace#2002
cjol wants to merge 4 commits into
mainfrom
think-workspace

Conversation

@cjol

@cjol cjol commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

This PR adds an opt-in @cloudflare/think/experimental/computer entrypoint for using Cloudflare Computer as a Think workspace. Existing Think agents continue to use @cloudflare/shell and just-bash unless they opt in. It consumes the shared client compatibility added by cloudflare/computer#26.

Why

  • Computer provides a durable filesystem plus native shell execution, while the current Think workspace runs commands through the in-process just-bash fallback.
  • Replacing the default workspace would change execution behavior and hide existing workspace data. There is no migration between the legacy @cloudflare/shell tables and Computer vfs_* tables.
  • The integration therefore lives behind an experimental export and optional peer dependency, so each Think class can opt in independently.
  • Local and remote Computer clients now share the upstream useThink compatibility surface. Keeping a second filesystem adapter in Think would duplicate path, stat, and missing-file translations and allow the two implementations to drift.

Public API Surface

All additions are under the new experimental export.

Symbol Kind Notes
createComputerWorkspace Function Constructs a locally owned Computer with useThink: true
adaptComputer Function Validates and narrows a compatible getWorkspace() client without wrapping it
ComputerWorkspace Type A Computer client intersected with Think WorkspaceLike
LocalComputerWorkspace Type A local Computer intersected with Think WorkspaceLike
Computer Class re-export Alias for the current Computer Workspace class
getWorkspace Function re-export Resolves local and remote Computer clients
withWorkspace Mixin re-export Adds Computer ownership to another Durable Object class
WorkspaceServiceProxy Worker entrypoint re-export Connects the Worker shell backend to the owning Think instance
WorkerShellBackend Class re-export Worker Loader shell backend
ComputerOptions Type Options accepted by createComputerWorkspace
WorkspaceClient Type Local or remote Computer client type
WorkerShellBackendOptions Type Worker shell backend configuration
WorkerShellFetcher Type Fetcher contract used by the Worker backend

Architectural Changes

Think.workspace
├── default: @cloudflare/shell Workspace
│   └── built-in bash tool uses just-bash
└── opt-in override: @cloudflare/computer
    ├── owner enables useThink once
    ├── local and remote clients expose the same Think methods
    ├── native fs, runtime, git, assets, and artifacts stay available
    └── built-in bash tool uses Computer shell exec

Code Changes

  • Adds the experimental Computer entrypoint and preserves the native Computer object for both local ownership and remote clients.
  • Makes adaptComputer() a runtime assertion because the owner-side useThink option cannot be inferred across RPC. It returns the original client rather than a compatibility wrapper.
  • Teaches createWorkspaceTools() to use a workspace native shell when available while retaining the existing just-bash fallback.
  • Adds the host bridge that lets WorkspaceServiceProxy obtain the stub for a Computer owned by the Think Durable Object.
  • Adds the optional Computer peer, build entrypoint, package export, changeset, and user documentation.

Compatibility

  • This is an additive minor change. Think default workspace, direct workspace methods, stored data, and just-bash execution remain unchanged.
  • Opting in starts with a separate Computer workspace. Existing legacy files remain untouched but are not visible through Computer, and Computer files are not visible after switching back.
  • Consumers importing the experimental entrypoint need @cloudflare/computer@>=0.1.1 <0.2.0.
  • The development dependency uses the published @cloudflare/computer@^0.1.1 release, which includes the shared Think compatibility surface from computer: Carry Think methods onto clients computer#26.

@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cc8918b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@cloudflare/think Minor
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cjol
cjol force-pushed the think-workspace branch from 41f4742 to 7eab151 Compare July 30, 2026 13:42
@pkg-pr-new

pkg-pr-new Bot commented Jul 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2002

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2002

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2002

create-think

npm i https://pkg.pr.new/create-think@2002

hono-agents

npm i https://pkg.pr.new/hono-agents@2002

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2002

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2002

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2002

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2002

commit: cc8918b

@cjol
cjol force-pushed the think-workspace branch from 7eab151 to dc177ef Compare July 30, 2026 13:58
cjol added 2 commits July 30, 2026 21:29
Computer now applies its Think filesystem compatibility methods to local and remote clients when the owner enables useThink. Keeping the same translations in Think creates two implementations that can drift and hides the native Computer client behind an adapter object.

Make ComputerWorkspace an intersection of WorkspaceClient and WorkspaceLike, and reduce adaptComputer() to a runtime assertion that narrows the owner's runtime configuration. The client remains unchanged, so its filesystem, shell, git, artifact, and asset surfaces stay directly available.

Require @cloudflare/computer 0.1.0-alpha.2 as the optional peer. Use the Computer pull request preview as the development dependency until that alpha is published, so fresh installs test the new client contract without a local package link.
@cjol cjol changed the title Add experimental Computer workspace integration to Think feat(think): add experimental Computer workspace Jul 30, 2026
@cjol

cjol commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

@aron-cf now that Computer #26 and workspace #24 have merged, could you publish "@cloudflare/computer 0.1.0-alpha.2" from main? Once that's on npm I can swap the PR preview dep, regenerate lockfile, and undraft this PR.

@cjol
cjol marked this pull request as ready for review August 3, 2026 13:36

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +239 to +251
function getWorkspaceShell(
workspace: WorkspaceLike
): WorkspaceShellExecutor | undefined {
try {
const candidate = workspace as WorkspaceLike &
WorkspaceWithShellCapability & {
runtime?: WorkspaceShellExecutor;
};
return candidate[workspaceShellCapability] ?? candidate.runtime;
} catch {
return undefined;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Shell-style command tool can be swapped in for workspaces that cannot actually run commands

Any workspace object that merely has a runtime property is treated as able to run shell commands (candidate.runtime at packages/think/src/tools/workspace.ts:247) without checking that it can actually execute anything, so the assistant's command tool can be routed to something that cannot run commands and every command fails.
Impact: Users of a Computer workspace that has no shell execution configured lose the working command tool entirely instead of silently keeping the existing sandboxed fallback.

Duck-typed capability detection bypasses the just-bash fallback

getWorkspaceShell() (packages/think/src/tools/workspace.ts:239-251) returns candidate[workspaceShellCapability] ?? candidate.runtime with no validation that the returned value exposes a callable exec. createWorkspaceTools() (packages/think/src/tools/workspace.ts:220-234) then unconditionally picks createWorkspaceShellTool over createBashTool whenever that value is truthy.

A @cloudflare/computer workspace always exposes runtime, even when no WorkerShellBackend was passed to createComputerWorkspace(). The documentation added in this PR states the Computer shell path applies "When a WorkerShellBackend is configured" (docs/think/tools.md:135-136), implying the just-bash fallback otherwise, but the code never falls back once runtime exists. The same applies to any custom WorkspaceLike that happens to define an unrelated runtime property.

A typeof shell?.exec === "function" guard (and ideally a check that a shell backend exists) would restore the intended fallback.

Suggested change
function getWorkspaceShell(
workspace: WorkspaceLike
): WorkspaceShellExecutor | undefined {
try {
const candidate = workspace as WorkspaceLike &
WorkspaceWithShellCapability & {
runtime?: WorkspaceShellExecutor;
};
return candidate[workspaceShellCapability] ?? candidate.runtime;
} catch {
return undefined;
}
}
function getWorkspaceShell(
workspace: WorkspaceLike
): WorkspaceShellExecutor | undefined {
try {
const candidate = workspace as WorkspaceLike &
WorkspaceWithShellCapability & {
runtime?: WorkspaceShellExecutor;
};
const shell = candidate[workspaceShellCapability] ?? candidate.runtime;
return typeof shell?.exec === "function" ? shell : undefined;
} catch {
return undefined;
}
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 220 to 234
if (options.bash !== false) {
tools.bash = createBashTool({
...(typeof options.bash === "object" ? options.bash : {}),
ops: workspaceBashOps(workspace)
});
const bashOptions =
typeof options.bash === "object" ? options.bash : undefined;
const shell = getWorkspaceShell(workspace);
tools.bash = shell
? createWorkspaceShellTool({
shell,
timeout: bashOptions?.timeout,
maxOutputBytes: bashOptions?.maxOutputBytes
})
: createBashTool({
...bashOptions,
ops: workspaceBashOps(workspace)
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Command-tool safety settings such as disabling network access are silently ignored on the new workspace

Configured limits for the command tool — network access and file snapshot caps — are dropped when the new workspace path is used (only timeout and maxOutputBytes are forwarded at packages/think/src/tools/workspace.ts:224-229), so a setting that was meant to keep commands offline has no effect.
Impact: An agent explicitly configured to run commands without network access can still reach the network after opting into the experimental workspace.

Dropped options in the Computer shell branch

BashToolOptions supports timeout, network, maxWorkspaceFiles, maxWorkspaceFileBytes, and maxOutputBytes (packages/think/src/tools/workspace.ts:1153-1160). Think forwards the user's workspaceBash object straight through (packages/think/src/think.ts:5600-5602).

In the new branch only timeout and maxOutputBytes are passed to createWorkspaceShellTool; network in particular is silently discarded, whereas the just-bash path defaults to network disabled and only enables it when network: true. Either the shell path should honor/translate these options, or createWorkspaceTools should surface an error/warning when unsupported options are supplied so the difference is not silent.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 220 to 234
if (options.bash !== false) {
tools.bash = createBashTool({
...(typeof options.bash === "object" ? options.bash : {}),
ops: workspaceBashOps(workspace)
});
const bashOptions =
typeof options.bash === "object" ? options.bash : undefined;
const shell = getWorkspaceShell(workspace);
tools.bash = shell
? createWorkspaceShellTool({
shell,
timeout: bashOptions?.timeout,
maxOutputBytes: bashOptions?.maxOutputBytes
})
: createBashTool({
...bashOptions,
ops: workspaceBashOps(workspace)
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Network isolation setting for the built-in command tool is dropped on the experimental Computer workspace

When the workspace exposes a native shell, createWorkspaceTools() builds the bash tool through createWorkspaceShellTool and forwards only timeout and maxOutputBytes (packages/think/src/tools/workspace.ts:224-229). The network option from workspaceBash (packages/think/src/think.ts:5600-5602) is silently discarded. In the existing just-bash path network access is disabled unless network: true is set; on the new path model-authored shell scripts run with whatever egress the Computer shell backend allows, with no way to restrict it. Model-generated commands are untrusted input, so this weakens the sandbox default for anyone opting into the experimental workspace.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@cjol
cjol marked this pull request as draft August 3, 2026 16:14
@cjol cjol closed this Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant